#!/usr/bin/python3
# -*-coding:utf-8 -*

"""  Fichier regroupant les fonctions, classes de manipulation des composants graphique du jeu """

# IMPORTS
from os.path import join
from config import TILE_WIDTH,TILE_HEIGHT
from config import PIPE,COMMA
from config import GRAPHIC_FOLDER,TILESET_FOLDER,SPRITE_FOLDER
from config import EXT_METADATA,EXT_TILESET,EXT_SPRITE
from config import UP,DOWN,RIGHT,LEFT
from config import NB_SPRITE_FRAME,SPRITE_WIDTH
from utils import loadImg,getCoordXY

class GraphicalComponents (object):

    """
        Classe de gestion des éléments graphiques
        
        Paramètres:
        dict dictTilesets -  dictionnaire regroupant les tilesets
        dict dictSprites -  dictionnaire regroupant les sprites
        
    """

    def __init__(self):
        """ Constructeur de la classe """
        self.dictTilesets={}
        self.dictSprites={}

    def getitem(self,name,typeGraphic):
        """
            Fonction de récupération d'un élément du tileset. Renvois le tileset s'il exixte,
            sinon le crée avant de la renvoyer. 
            
            Paramètre nommé :
            str name - nom du tileset
            str typeGraphic - typeGraphic de l'élément rechercher
            
            Retour :
            GraphicalTileset ou Sprite
        
        """
        # Test des entrées
        if typeGraphic.lower() not in ['tileset','sprite']:
            raise SystemExit,'%s n\'est pas un type connu (tileset ou sprite).'%(type)
        else:
            # Gestion des tilesets
            if typeGraphic=='tileset':
                if name not in self.dictTilesets:
                    # Création du tileset et ajout au dictionnaire
                    self.dictTilesets[name]=GraphicalTileset(name)
                return self.dictTilesets[name]
            # Gestion des sprites
            elif typeGraphic=='sprite':
                if name not in self.dictSprites:
                    # Création du Sprite et ajout au dictionnaire
                    self.dictSprites[name]=GraphicalTilesetSprite(name)
                return self.dictSprites[name]

class GraphicalTileset (object):

    """
        Classe de gestion d'un tileset
        
        Paramètres:
        str tilesetName - nom de l'image
        dict dictTiles -  dictionnaire des éléments du tileset
            
    """

    def __init__(self,tilesetName):
        """ 
            Constructeur de la classe 
            
            Paramètres nommées:
            str tilesetName - nom de l'image
        
        """
        self.tilesetName=tilesetName
        self.dictTiles=self.__load(tilesetName)

    def __load(self,tilesetName):
        """ 
            Fonction de chargement d'un tileset. 
            
            Paramètres nommées:
            str tilesetName - nom de l'image
            
            Retour:
            dict - {'tileName' : GraphicalTile, 'tileName' : GraphicalTile, ...}
        
        """
        # Chemin vers le fichier metadata
        pathImgMetadata=join(GRAPHIC_FOLDER,TILESET_FOLDER,'%s.%s'%(tilesetName,EXT_METADATA))
        # Dictionnaire associatif : {'tileName' : GraphicalTile, 'tileName' : GraphicalTile, ...)
        tile={}

        # Ouverture du fichier metadata
        try:
            fileMetadata=open(pathImgMetadata,'rU')
        except IOError,message:
            print 'Impossible d\'ouvrir l\'image : %s.%s'%(tilesetName,EXT_METADATA)
            raise SystemExit,message

        # Récupération de l'image
        image=loadImg('%s.%s'%(tilesetName,EXT_TILESET),join(GRAPHIC_FOLDER,TILESET_FOLDER))
        # Découpage du fichier image
        tileTable=cutImage(image[0])

        # Extraction des données metadata
        for line in fileMetadata:
            # Suppression des caractères spéciaux
            line=line.strip()
            # Traitement des lignes non vide
            if len(line)>0:
                # Extraction des informations des métadatas
                tilePoint,tileInfo=line.split() # titlePoint = 'x,y', tileInfo = 'level|name|crossDirection'
                coord=getCoordXY(tilePoint,COMMA)
                listInfo=tileInfo.split(PIPE)
                # Création de l'élément et ajout au dictionnaire
                tile[listInfo[1]]=GraphicalTile(listInfo[1]
                                               ,tileTable[coord[0]][coord[1]]
                                               ,int(listInfo[0])
                                               ,listInfo[2]
                                               )
        return tile

    def __getitem__(self,tileName):
        """
            Fonction de récupération d'un élément du tileset, lève une exception KeyError sinon
            
            Paramètre nommé :
            str tileName - nom de l'élément désiré
            
            Retour :
            GraphicalTile
        
        """
        if tileName not in self.dictTiles:
            raise KeyError,'L\'élément %s n\'éxiste pas dans le tileset %s'%(tileName,self.tilesetName)
        return self.dictTiles[tileName]

    def __repr__(self):
        """
            Fonction de représentation de l'objet GraphicalTileset
            
            Retour :
            string
            
        """
        msg="***\nTileset Name : %s\n***\n"%(self.tilesetName)
        for graphicalTile in self.dictTiles.items():
            msg+="%s\n"%(str(graphicalTile))
        return msg+"***\n"

    def __str__(self):
        """
            Fonction appelée quand on souhaite afficher l'objet grâce à la 
            fonction 'print' ou le convertir en chaîne grâce au constructeur
            'str'. On redirige sur __repr__
            
            Retour :
            string
        
        """
        return repr(self)

class GraphicalTile (object):

    """
        Classe de gestion d'un élément d'un tileset
        
        Paramètres:
        str name - nom de l'élément
        surface image -  représentation graphique de l'élément
        int level - niveau de superposition de l'élément graphique
        dict dictCross - dictionnaire déterminant si le l'élément est traversable ou non dans la direction donnée
        
    """

    def __init__(self,name,image,level,listCross):
        """ 
            Constructeur de la classe
        
            Paramètres nommés:
            str name - nom de l'élément
            surface image -  représentation graphique de l'élément
            int level - niveau de superposition de l'élément graphique
            list listCross - liste des directions déterminant si l'élément est traversable ou non
            
        """
        # Variable
        self.name=name
        self.image=image
        self.level=level
        # Chargement de la traversabilité de la case
        self.__loadCross(listCross)

    def __loadCross(self,listCross):
        """
            Méthode de transformation de la liste d'entrée en dictionnaire déterminant si l'élément est traversable ou non
            
            Paramètres nommés:
            list listCross - liste des directions déterminant si l'élément est traversable ou non
        """
        # Test de la taille de l'élément
        if len(listCross)!=8:
            raise SystemExit,'La liste des directions déterminant si l\'élément est traversable ou non doit contenir 8 directions : %s.'%(str(len(listCross)))
        else:
            # Extraction
            listBool=[bool(int(item)) for item in listCross]
            # TODO : Gérer dynamiquement l'affectation des directions
            self.dictCross={UP: listBool[0]
                           ,UP+RIGHT: listBool[1]
                           ,RIGHT: listBool[2]
                           ,DOWN+RIGHT: listBool[3]
                           ,DOWN: listBool[4]
                           ,DOWN+LEFT: listBool[5]
                           ,LEFT: listBool[6]
                           ,UP+LEFT: listBool[7]
                           }

    def __repr__(self):
        """
            Fonction de représentation de l'objet GraphicalTile
            
            Retour :
            string
            
        """
        return 'Name : %s - Level : %s - Cross : %s'%(self.name,self.level,self.dictCross)

    def __str__(self):
        """
            Fonction appelée quand on souhaite afficher l'objet grâce à la 
            fonction 'print' ou le convertir en chaîne grâce au constructeur
            'str'. On redirige sur __repr__
            
            Retour :
            string
        
        """
        return repr(self)

class GraphicalTilesetSprite(object):

    """
        Classe de gestion d'un tileset de type sprite
        
        Paramètres:
        str tilesetSpriteName - nom de l'image
        dict dictSpriteState -  dictionnaire des animation du sprite
            Exemple : { UP : [Surface, Surface, ...], DOWN : [Surface, Surface, ...], ...}
            
    """

    def __init__(self,tilesetSpriteName):
        """ 
            Constructeur de la classe 
            
            Paramètres nommées:
            str tilesetSpriteName - nom de l'image
        
        """
        self.tilesetSpriteName=tilesetSpriteName
        self.dictSpriteState=self.__load(tilesetSpriteName)

    def __load(self,tilesetSpriteName):
        """ 
            Fonction de chargement d'un tileset. 
            
            Paramètres nommées:
            str tilesetSpriteName - nom de l'image
            
            Retour:
            dict - { UP : [Surface, Surface, ...], DOWN : [Surface, Surface, ...], ...}
        
        """
        # Récupération de l'image
        image=loadImg('%s.%s'%(tilesetSpriteName,EXT_SPRITE),join(GRAPHIC_FOLDER,SPRITE_FOLDER))
        # Gestion du nombre de frame par image
        width,height=self.__getSpiteCutSize(image[0])
        # Découpage du fichier image
        tileTable=cutImage(image[0],width,height)
        # Inversion lignes/colonnes
        tileTable=zip(*tileTable)

        # Création du dictionnaire
        # TODO : Gérer dynamiquement l'affectation des directions
        return {UP: tileTable[NB_SPRITE_FRAME-1]
               ,DOWN: tileTable[0]
               ,LEFT: tileTable[1]
               ,RIGHT: tileTable[2]
               ,UP+RIGHT: tileTable[NB_SPRITE_FRAME-1]
               ,UP+LEFT: tileTable[NB_SPRITE_FRAME-1]
               ,DOWN+RIGHT: tileTable[0]
               ,DOWN+LEFT: tileTable[0]
               }

    def __getSpiteCutSize(self,image):
        """
            Fonction de récupération de la dimention des frame du sprite
            
            Paramètre nommé:
            surface image - objet image du sprite
            
            Retour:
            tuple - (width, height)
            
        """
        x,y=image.get_size()
        return (SPRITE_WIDTH,y//NB_SPRITE_FRAME)

    def __getitem__(self,direction):
        """
            Fonction de récupération d'une liste de frame du sprite à partir d'une direction
            
            Paramètre nommé :
            int direction - direction désirée
            
            Retour :
            list - [ Surface, Surface, Surface, ...]
        
        """
        return self.dictSpriteState[direction]

def cutImage(image,width = TILE_WIDTH,height = TILE_HEIGHT):
    """ 
        Fonction de découpage d'un tileset en tableau d'image.
        
        Paramètres nommés :
        str name - nom du fichier image.
        str folder - nom du répertoire ou se situe l'image
        int width - largeur, en pixel, de la découpe
        int height - hauteur, en pixel, de la découpe
        
        Retour :
        tableau( tableau (surface, surface...),
                 tableau (surface, surface...),
                 ...
                )
    """
    # Variable
    tileTable=[]
    # Chargement de l'image
    imageWidth,imageHeight=image.get_size()
    # Découpe selon les abscisses
    for col in range(0,imageWidth/width):
        line=[]
        # Découpe selon les ordonnées
        for row in range(0,imageHeight/height):
            # Création d'une surface à découper dans l'image
            rect=(col*width,row*height,width,height)
            # Ajout à notre ligne
            line.append(image.subsurface(rect))
        # Ajout de la ligne découpée à la table
        tileTable.append(line)
    return tileTable

"""
    Initialisation de l'objet de gestion des composants graphiques
"""
GC=GraphicalComponents()
